package aiyh.utils.recordset;

import aiyh.utils.Util;
import aiyh.utils.annotation.recordset.CaseConversion;
import aiyh.utils.excention.TypeNonsupportException;
import weaver.conn.RecordSet;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.*;

/**
 * @author EBU7-dev1-ayh
 * create 2021/12/21 0021 11:03
 */


public class ResultMapper {

    private static Map<Class<?>, TypeHandler> typeHandler = new HashMap<>();

    static {
        IntegerTypeHandler integerTypeHandler = new IntegerTypeHandler();
        typeHandler.put(String.class, new StringTypeHandler());
        typeHandler.put(Integer.class, integerTypeHandler);
        typeHandler.put(int.class, integerTypeHandler);
        typeHandler.put(byte.class, integerTypeHandler);
        typeHandler.put(short.class, integerTypeHandler);
        typeHandler.put(long.class, integerTypeHandler);
        typeHandler.put(Long.class, integerTypeHandler);
        typeHandler.put(Boolean.class, new BooleanTypeHandler());
        typeHandler.put(boolean.class, new BooleanTypeHandler());
        typeHandler.put(Date.class, new DataTypeHandler());
    }

    public <T> T mapperResult(RecordSet rs, Method method, Class<T> tClass) {
        if (tClass.equals(void.class)) {
            return null;
        }
        try {
            if (tClass.equals(List.class)) {
                tClass = (Class<T>) ArrayList.class;
            }
            if (tClass.equals(Map.class)) {
                tClass = (Class<T>) HashMap.class;
            }
            if (ResultMapper.typeHandler.containsKey(tClass)) {
                rs.next();
                return (T) ResultMapper.typeHandler.get(tClass).getValue(rs, 1,null);
            }
            T t = tClass.newInstance();
            if (t instanceof Collection) {
//                集合求出泛型
                //获取返回值的类型
                Type genericReturnType = method.getGenericReturnType();
                Type actualTypeArgument = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
                Class<?> rawType = this.getRawType(actualTypeArgument);
                if (rawType.equals(Map.class)) {
                    rawType = HashMap.class;
                }
                while (rs.next()) {
                    Object o = rawType.newInstance();
                    Object object = getObject(rs, o,method);
                    ((Collection<? super Object>) t).add(object);
                }
                return t;
            }
            if (t instanceof Map) {
//                map
                Type genericReturnType = method.getGenericReturnType();
                Type actualTypeArgument = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
                Class<?> rawType = this.getRawType(actualTypeArgument);
                if (rawType.equals(List.class)) {
                    rawType = (Class<T>) ArrayList.class;
                }
                if (rawType.equals(Map.class)) {
                    rawType = HashMap.class;
                }
                Object o = rawType.newInstance();
                if (o instanceof Map || o instanceof Collection) {
                    throw new TypeNonsupportException("An unsupported return type!");
                }
                if(rs.next()){
                    return (T) getObject(rs, t,method);
                }
                return null;
            }
            if (t.getClass().isArray()) {
                throw new TypeNonsupportException("An unsupported return type!");
            }
            if(rs.next()){
                return (T) getObject(rs, t,method);
            }
            return null;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Object getObject(RecordSet rs, Object o,Method method) {
        CaseConversion annotation = method.getAnnotation(CaseConversion.class);
        boolean enable = annotation == null ? true : annotation.value();
        String[] columnName = rs.getColumnName();
        String[] columnTypeName = rs.getColumnTypeName();
        try {
                for (int i = 0; i < columnName.length; i++) {
                    String columnType = columnTypeName[i];
                    if (o instanceof Map) {
                        if ("int".equalsIgnoreCase(columnType) ||
                                "long".equalsIgnoreCase(columnType) ||
                                "number".equalsIgnoreCase(columnType) ||
                                "MEDIUMINT".equalsIgnoreCase(columnType) ||
                                "TINYINT".equalsIgnoreCase(columnType) ||
                                "SMALLINT".equalsIgnoreCase(columnType) ||
                                "BIGINT".equalsIgnoreCase(columnType) ||
                                "INTEGER".equalsIgnoreCase(columnType)) {
                            if(enable){
                                ((Map<? super Object, ? super Object>) o).put(Util.toCamelCase(columnName[i]), rs.getInt(i + 1));
                                continue;
                            }
                            ((Map<? super Object, ? super Object>) o).put(columnName[i], rs.getInt(i + 1));
                            continue;
                        }
                        if ("FLOAT".equalsIgnoreCase(columnType) ||
                                "DOUBLE".equalsIgnoreCase(columnType) ||
                                "DECIMAL".equalsIgnoreCase(columnType)) {
                            if(enable){
                                ((Map<? super Object, ? super Object>) o).put(Util.toCamelCase(columnName[i]), rs.getFloat(i + 1));
                                continue;
                            }
                            ((Map<? super Object, ? super Object>) o).put(columnName[i], rs.getFloat(i + 1));
                            continue;
                        }
                        if(enable){
                            ((Map<? super Object, ? super Object>) o).put(Util.toCamelCase(columnName[i]), rs.getString(i + 1));
                            continue;
                        }
                        ((Map<? super Object, ? super Object>) o).put(columnName[i], rs.getString(i + 1));
                        continue;
                    }
                    if (o instanceof Collection) {
                        throw new TypeNonsupportException("An unsupported return type!");
                    }
                    if (o instanceof Number) {
                        if ("int".equalsIgnoreCase(columnType) ||
                                "long".equalsIgnoreCase(columnType) ||
                                "number".equalsIgnoreCase(columnType) ||
                                "MEDIUMINT".equalsIgnoreCase(columnType) ||
                                "TINYINT".equalsIgnoreCase(columnType) ||
                                "SMALLINT".equalsIgnoreCase(columnType) ||
                                "BIGINT".equalsIgnoreCase(columnType) ||
                                "INTEGER".equalsIgnoreCase(columnType)) {
                            return rs.getInt(i + 1);
                        }
                        if ("FLOAT".equalsIgnoreCase(columnType) ||
                                "DOUBLE".equalsIgnoreCase(columnType) ||
                                "DECIMAL".equalsIgnoreCase(columnType)) {
                            return rs.getFloat(i + 1);
                        }
                    }
                    if (o instanceof String) {
                        return rs.getString(i + 1);
                    }
                    if (o instanceof Boolean) {
                        return rs.getBoolean(i + 1);
                    }

                    BeanInfo beanInfo = Introspector.getBeanInfo(o.getClass(), Object.class);
                    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                        Class<?> propertyType = propertyDescriptor.getPropertyType();
                        Object value = null;
                        String fieldName = propertyDescriptor.getName();
                        Field declaredField = o.getClass().getDeclaredField(fieldName);
                        if(enable){
                            value = ResultMapper.typeHandler.get(propertyType) == null ? null : ResultMapper.typeHandler.get(propertyType).getValue(rs, Util.toUnderlineCase(fieldName),declaredField);
                        }else {
                            value = ResultMapper.typeHandler.get(propertyType) == null ? null : ResultMapper.typeHandler.get(propertyType).getValue(rs, fieldName,declaredField);
                        }
                        propertyDescriptor.getWriteMethod().invoke(o, value);
                    }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return o;
    }

    public Class<?> getRawType(Type type) {
        if (type instanceof Class) {
            return (Class) type;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type rawType = parameterizedType.getRawType();
            return (Class) rawType;
        } else if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            return Array.newInstance(getRawType(componentType), 0).getClass();
        } else if (type instanceof TypeVariable) {
            return Object.class;
        } else if (type instanceof WildcardType) {
            return getRawType(((WildcardType) type).getUpperBounds()[0]);
        } else {
            String className = type == null ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType, or GenericArrayType, but <" + type + "> is of type " + className);
        }
    }
}
